﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Upreader.Application.Model;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace Upreader.Engine
{
	/// <summary>
	/// The main class holding information for servers and processing connections
	/// </summary>
	public class ServerProcessor : IDisposable
	{
		#region Fields

        private readonly DownloadEngineContext _context;

		// holds a set of connections. Using concurrent stack ensures that the oldest connections will live the longest 
		// when playing with the number of connections which is slightly better for performance
		private readonly ConcurrentStack<ConnectionProcessor> _connections;

		private bool _disposed;

		#endregion // Fields

		#region Constructor

		public ServerProcessor(Server server,
            DownloadEngineContext context)
		{
			if (server == null)
				throw new ArgumentNullException("server");
            if (context == null)
                throw new ArgumentNullException("context");

            _context = context;

			_connections = new ConcurrentStack<ConnectionProcessor>();
			
			UpdateServerInformation(server);
		}

		#endregion // Constructor

		#region Properties

		/// <summary>
		/// Get a set of connections associated with this server
		/// </summary>
		public IEnumerable<ConnectionProcessor> Connections
		{
			get
			{
				return _connections;
			}
		}

		public Server ServerInformation
		{
			get;
			protected set;
		}

		#endregion // Properties

		#region Methods

		/// <summary>
		/// Pushes the server information and updates where nessesary
		/// </summary>
		public void UpdateServerInformation(Server serverInformation)
		{
			if (serverInformation == null)
				throw new ArgumentNullException("serverInformation");

			Server currentServerInformation = ServerInformation;

			// if there already was a server information
			if (currentServerInformation != null)
			{
				// if a fundamental property changed affecting existing connections
				if (currentServerInformation.Address != serverInformation.Address ||
					currentServerInformation.IsAuthenticationRequired != serverInformation.IsAuthenticationRequired || 
					currentServerInformation.IsSecuredConnection != serverInformation.IsSecuredConnection ||
					currentServerInformation.Password != serverInformation.Password ||
					currentServerInformation.Port != serverInformation.Port ||
					currentServerInformation.UserName != serverInformation.UserName ||
                    currentServerInformation.Enabled != serverInformation.Enabled)
				{
					// close all current connections so we can restart them later
					UpdateConnections(0);
				}
			}

            // assign the new server details
            ServerInformation = serverInformation;

            // adjust for the desired number of connections
			UpdateConnections(serverInformation.ConnectionCount);
		}

        public long CalculateTotalBytesSent()
        {
            return _connections.Sum(x => x.NntpClient.Statistics.TotalBytesSent);
        }

        public long CalculateTotalBytesReceived()
        {
            return _connections.Sum(x => x.NntpClient.Statistics.TotalBytesReceived);
        }

        public void Close()
        {
            UpdateConnections(0);
        }

		#endregion // Methods

		#region Helpers

		private void ThrowIfDisposed()
		{
			if (_disposed)
				throw new ObjectDisposedException(null);
		}

		private void UpdateConnections(int desired)
		{
			int connectionsToAdd = desired - _connections.Count;

            List<Task> closingTasks = new List<Task>();

			// first keep on removing if required
			while (connectionsToAdd < 0)
			{
				ConnectionProcessor connection;
				if (_connections.TryPop(out connection))
				{
					Task closingTask = connection.Stop();

                    closingTasks.Add(closingTask);

                    var eventHandler = ConnectionRemoved;
                    if (eventHandler != null)
                        eventHandler(this, new ConnectionProcessorEventArgs(connection));
				}

				connectionsToAdd++;
			}

            // wait for all closing connections before opening new connections or returning
            // this because if we want to reconnect, we dont want to go over the connectionLimit (Count)
            Task.WaitAll(closingTasks.ToArray());

			// then, keep on adding if required
            while (connectionsToAdd > 0)
            {
                ConnectionProcessor connection = new ConnectionProcessor(ServerInformation, _context)
                {
                    Enabled = ServerInformation.Enabled
                };

                _connections.Push(connection);

                var eventHandler = ConnectionAdded;
                if (eventHandler != null)
                    eventHandler(this, new ConnectionProcessorEventArgs(connection));

                connectionsToAdd--;
            }
		}

		#endregion // Helpers

		#region Events

        public event EventHandler<ConnectionProcessorEventArgs> ConnectionAdded;

        public event EventHandler<ConnectionProcessorEventArgs> ConnectionRemoved;

		#endregion // Events

		#region Disposable

		public void Dispose()
		{
			_disposed = true;

            // Close all connections
            UpdateConnections(0);
		}

		#endregion // Disposable
	}
}
